
#include "flowrate.hpp"
#include "util.hpp"
#include <algorithm>
#include <unistd.h>
using namespace flowrate;
// timeRemLimit is the maximum Status.TimeRem value.
static const int64_t timeRemLimit= 999*int64_t(kHour) + 59*int64_t(kMinute) + 59*int64_t(kSecond);

inline double seconds(int64_t w) {return double(w / kSecond) + double( (w % kSecond)*1e-6); }

Monitor::Monitor(int64_t sampleRate, int64_t windowSize):active(true)
{
    sampleRate = clockRound(sampleRate);
    if( sampleRate <= 0 ) sampleRate = 5* kClockRate;
    if( windowSize <= 0 ) windowSize = kSecond;
    int64_t now = clock();

    start = now;
    rWindow = seconds(windowSize);
    sLast=now;
    sRate = sampleRate;
    tLast = now;
}


int64_t Monitor::Update(int n)
{
    std::unique_lock<std::mutex> lock(m);
    update(n);
    return n;
}

int64_t Monitor::update(int n)
{
    if(!active) return 0;

    int64_t now=clock();
    if( n > 0 ) tLast = now;

    sBytes += int64_t(n);

    int64_t sTime = now-sLast;
    if(sTime>sRate)
    {
        double t = seconds(sTime);
        rSample = double(sBytes) / t;
        if(rSample > rPeak) rPeak = rSample;

        // Exponential moving average using a method similar to *nix load
		// average calculation. Longer sampling periods carry greater weight.
        if(samples > 0)
        {
            double w= exp(-t / rWindow);
            rEMA = rSample + w*(rEMA - rSample);
        } else rEMA = rSample;

        reset(now);
    }
    return now;

}

void Monitor::setREMA(double frEMA)
{
    std::unique_lock<std::mutex> lock(m);
    rEMA = frEMA;
    samples++;
}

int64_t Monitor::done()
{
    std::unique_lock<std::mutex> lock(m);
    int64_t now=update(0);
    if( sBytes > 0 ) reset(now);

    active = false;
    tLast = 0;
    return bytes;
}

void Monitor::reset(int64_t sampleTime)
{
    bytes += sBytes;
    samples++;
    sBytes = 0;
    sLast = sampleTime;
}

Status Monitor::status()
{
    std::unique_lock<std::mutex> lock(m);
    int64_t now = update(0);
    Status s{
        .active = active,
        .start = clockToTime(start),
        .duration = sLast - start,
        .idle = now - tLast,
        .bytes = bytes,
        .samples = samples,
        .peakRate = round(rPeak),
        .bytesRemain = tBytes - bytes,
        .progress = percentOf(double(bytes),double(tBytes))
    };
    
    if( s.bytesRemain < 0 ) s.bytesRemain = 0;
    if( s.duration > 0 )
    {
        double rAvg=double(s.bytes) / seconds(s.duration);
        s.averageRate = round(rAvg);
        if(s.active)
        {
            s.instantaneousRate = round(rSample);
            s.currentRate = round(rEMA);
            if(s.bytesRemain > 0)
            {
                double tRate=0.8*rEMA+0.2*rAvg;
                if( tRate > 0)
                {
                    double ns =  double(s.bytesRemain) / tRate *1e9;
                    ns = std::min(ns,double(timeRemLimit));
                    s.timeRemain = clockRound(TimeDuration(ns));
                }
            }
        }
    }
    return s;
}


void Monitor::setTransferSize(int64_t size)
{
    size = std::max(int64_t(0),size);
    std::unique_lock< std::mutex > lock(m);
    tBytes = size;
}

TimeDuration Monitor::waitNextSample(TimeDuration now)
{
    const int minWait = 5 * kMillisecond;
    auto current = sLast;

    //sleep until the last sample time changes (ideally, just one iteration)
    while(sLast == current && active)
    {
        auto d = current + sRate - now;
        std::unique_lock<std::mutex> lock(m);
        d = std::max(d,(long)minWait);
        sleep( seconds(d) );
        now = update(0);
    }
    return now;
}